package ift605.EquationWithTrainingAgents.behavior.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

import ift605.EquationWithTrainingAgents.agent.PoolAgent;
import ift605.EquationWithTrainingAgents.struct.DerivationInfo;
import ift605.EquationWithTrainingAgents.struct.DerivationStep;
import ift605.EquationWithTrainingAgents.struct.LearningMessage;
import jade.core.AID;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

public abstract class DeriverBehavior extends CyclicBehaviour {
	private static final long serialVersionUID = 1L;

	public abstract boolean derive(List<DerivationInfo> aMessageList);
	
	@Override
	public void action() {
		ACLMessage request = null;
		MessageTemplate mt = MessageTemplate.MatchConversationId(LearningMessage.DERIVE);
		request = myAgent.receive(mt);
		
		if(request != null)
		{
			//System.out.println(myAgent.getLocalName() + " received message");
			
			if(((PoolAgent) myAgent).isValidPerformative(request.getPerformative())){
				try { 
					LearningMessage initialMess = (LearningMessage)request.getContentObject();
					List<DerivationInfo> derivationInfos = new ArrayList<DerivationInfo>();
					derivationInfos.add(new DerivationInfo(initialMess,
							initialMess.tryDerive ? 
									null : 
									initialMess.derivationSteps.get(initialMess.currentStep).getVariation()));

					// Do a derivation step depending on his specific behavior
					boolean isTerminate = derive(derivationInfos);
					
					if(isTerminate){
						for(DerivationInfo derivationInfo : derivationInfos){
							// if we are trying to derived the equation
							if(derivationInfo.Mess.tryDerive)
							{
								//System.out.println(myAgent.getLocalName() + " try to derive " + derivationInfo.Mess.getClass().getSimpleName());
								// Add his agent AID to the list of each step of derivation
								derivationInfo.Mess.derivationSteps.add(new DerivationStep(myAgent.getAID(), derivationInfo.Variation));
								derivationInfo.Mess.currentStep = derivationInfo.Mess.derivationSteps.size();
								
								// Ask to papa to verify the derived
								ACLMessage response = new ACLMessage(derivationInfo.Mess.requestID);
								response.addReceiver(new AID("Papa", AID.ISLOCALNAME));
								response.setConversationId(LearningMessage.VERIFY);
								response.setContentObject(derivationInfo.Mess);
								myAgent.send(response);
							}
							else // we just resolve the equation
							{
								// take a step FORWARD
								derivationInfo.Mess.currentStep++;
								
								if(derivationInfo.Mess.currentStep < derivationInfo.Mess.derivationSteps.size())
								{
									// send it to next step
									ACLMessage response = new ACLMessage(derivationInfo.Mess.requestID);
									response.addReceiver(derivationInfo.Mess.derivationSteps.get(derivationInfo.Mess.currentStep).getAgent());
									response.setConversationId(LearningMessage.DERIVE);
									response.setContentObject(derivationInfo.Mess);
									myAgent.send(response);
								}
								else
								{
									// Send to Papa
									ACLMessage response = new ACLMessage(derivationInfo.Mess.requestID);
									response.addReceiver(new AID("Papa", AID.ISLOCALNAME));
									response.setConversationId(LearningMessage.VERIFY);
									response.setContentObject(derivationInfo.Mess);
									myAgent.send(response);
								}
							}
						}
					}
				} 
				catch(Exception e){
					e.printStackTrace();
				}
			}
		}
		else
		{
			block();
		}
	}
	
	public static Object Clone(Object target)
	{
		try
		{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(target);
			oos.flush();
			oos.close();
			bos.close();
			
			ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
			return (Object) new ObjectInputStream(bais).readObject();
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
}
